﻿/*
|| 
|| MIRIA - http://miria.codeplex.com
|| Copyright (C) 2008-2011 Generoso Martello <generoso@martello.com>
||
|| This program is free software: you can redistribute it and/or modify
|| it under the terms of the GNU General Public License as published by
|| the Free Software Foundation, either version 3 of the License, or
|| (at your option) any later version.
||  
|| This program is distributed in the hope that it will be useful,
|| but WITHOUT ANY WARRANTY; without even the implied warranty of
|| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|| GNU General Public License for more details.
|| 
|| You should have received a copy of the GNU General Public License
|| along with this program. If not, see
|| <http://www.gnu.org/licenses/>.
|| 
*/

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

using System.Threading;

using System.Collections.Generic;

namespace MIRIA.Animations
{
    public class Animator
    {
        private static object _lockobj = new object();

        public delegate bool ScriptDelegate();


        public static void AnimatePropertyStop(FrameworkElement element, string dependencyproperty)
        {
            if (element.Resources.Contains("MIRIA_sb_AnimateProperty_" + dependencyproperty))
            {
                Storyboard storyboard = (Storyboard)element.Resources["MIRIA_sb_AnimateProperty_" + dependencyproperty];
                storyboard.SkipToFill();
                storyboard.Stop();
            }
        }
        public static void AnimateProperty(FrameworkElement element, string dependencyproperty, double valuefrom, double valueto, double durationsec, double startdelay, EventHandler completedCallback)
        {
            Thread t = new Thread(new ThreadStart(delegate(){
                Thread.Sleep(TimeSpan.FromSeconds( startdelay ));
                element.Dispatcher.BeginInvoke(() =>
                {
                    AnimateProperty(element, dependencyproperty, valuefrom, valueto, durationsec, completedCallback);
                });
            }));
            t.Start();
        }

        public static void AnimateProperty(FrameworkElement element, string dependencyproperty, double valuefrom, double valueto, double durationsec, EventHandler completedCallback)
        {
            lock (_lockobj)
            {
                Storyboard storyboard;
                DoubleAnimation doubleanimation;
                Duration duration = new Duration(TimeSpan.FromSeconds(durationsec));



                if (element.Visibility == Visibility.Collapsed)
                {
                    element.Visibility = Visibility.Visible;
                }




                if (!element.Resources.Contains("MIRIA_sb_AnimateProperty_" + dependencyproperty))
                {
                    doubleanimation = new DoubleAnimation();
                    storyboard = new Storyboard();
                    storyboard.Children.Add(doubleanimation);


                    // composite animation properties
                    //
                    if (dependencyproperty == "Angle")
                    {
                        element.RenderTransformOrigin = new Point(0.5, 0.5);
                        TransformGroup tgr = _getTransformGroup(element);
                        RotateTransform rtr = new RotateTransform();
                        tgr.Children.Add(rtr);
                        element.Resources.Add("MIRIA_sb_RotateTransform", rtr);
                        Storyboard.SetTargetProperty(doubleanimation, new PropertyPath(dependencyproperty));
                        Storyboard.SetTarget(doubleanimation, rtr);
                    }
                    else if (dependencyproperty == "X")
                    {
                        element.RenderTransformOrigin = new Point(0.5, 0.5);
                        TransformGroup tgr = _getTransformGroup(element);
                        TranslateTransform ttr = new TranslateTransform();
                        tgr.Children.Add(ttr);
                        element.Resources.Add("MIRIA_sb_TranslateTransformX", ttr);
                        Storyboard.SetTargetProperty(doubleanimation, new PropertyPath(dependencyproperty));
                        Storyboard.SetTarget(doubleanimation, ttr);
                    }
                    else if (dependencyproperty == "Y")
                    {
                        element.RenderTransformOrigin = new Point(0.5, 0.5);
                        TransformGroup tgr = _getTransformGroup(element);
                        TranslateTransform ttr = new TranslateTransform();
                        tgr.Children.Add(ttr);
                        element.Resources.Add("MIRIA_sb_TranslateTransformY", ttr);
                        Storyboard.SetTargetProperty(doubleanimation, new PropertyPath(dependencyproperty));
                        Storyboard.SetTarget(doubleanimation, ttr);
                    }
                    else if (dependencyproperty == "Scale")
                    {
                        element.RenderTransformOrigin = new Point(0.5, 0.5);
                        TransformGroup tgr = _getTransformGroup(element);
                        ScaleTransform str = new ScaleTransform();
                        tgr.Children.Add(str);
                        element.Resources.Add("MIRIA_sb_ScaleTransform", str);
                        //
                        DoubleAnimation doubleanimation2 = new DoubleAnimation();
                        doubleanimation2.From = valuefrom;
                        doubleanimation2.To = valueto;
                        doubleanimation2.Duration = duration;
                        storyboard.Children.Add(doubleanimation2);
                        //
                        Storyboard.SetTargetProperty(doubleanimation, new PropertyPath("ScaleX"));
                        Storyboard.SetTarget(doubleanimation, str);
                        Storyboard.SetTargetProperty(doubleanimation2, new PropertyPath("ScaleY"));
                        Storyboard.SetTarget(doubleanimation2, str);
                    }
                    else if (dependencyproperty.StartsWith("Rotation"))
                    {
                        PlaneProjection ppr = _getPlaneProjection(element);
                        Storyboard.SetTargetProperty(doubleanimation, new PropertyPath(dependencyproperty));
                        Storyboard.SetTarget(doubleanimation, ppr);
                    }
                    else if (dependencyproperty.StartsWith("GlobalOffset") || dependencyproperty.StartsWith("LocalOffset"))
                    {
                        PlaneProjection ppr = _getPlaneProjection(element);
                        Storyboard.SetTargetProperty(doubleanimation, new PropertyPath(dependencyproperty));
                        Storyboard.SetTarget(doubleanimation, ppr);
                    }
                    else
                    {
                        Storyboard.SetTargetProperty(doubleanimation, new PropertyPath(dependencyproperty));
                        Storyboard.SetTarget(doubleanimation, element);
                    }



                    element.Resources.Add("MIRIA_sb_AnimateProperty_" + dependencyproperty, storyboard);

                }
                else
                {
                    storyboard = (Storyboard)element.Resources["MIRIA_sb_AnimateProperty_" + dependencyproperty];
                    doubleanimation = (DoubleAnimation)storyboard.Children[0];
                    storyboard.SkipToFill();
                    storyboard.Stop();
                }

                doubleanimation.From = valuefrom;
                doubleanimation.To = valueto;
                doubleanimation.Duration = duration;


                // composite animation properties
                //
                if (dependencyproperty == "Scale")
                {
                    DoubleAnimation doubleanimation2 = storyboard.Children[1] as DoubleAnimation;
                    doubleanimation2.From = valuefrom;
                    doubleanimation2.To = valueto;
                    doubleanimation2.Duration = duration;
                }

                storyboard.Duration = duration;

                storyboard.FillBehavior = FillBehavior.HoldEnd;
                storyboard.Completed += new EventHandler((object sender, EventArgs args) =>
                {
                    if (completedCallback != null)
                    {
                        ////// TODO: write custom event with element passed into args not as sender
                        completedCallback(element, args);
                        completedCallback = null;
                    }
                    //element.Resources.Remove("MIRIA_sb_AnimateProperty_" + dependencyproperty);
                });

                storyboard.Begin();
            }
        }

        public static Thread PlayScript(ScriptDelegate scriptdelegate)
        {
            Thread t = new Thread(new ThreadStart(delegate()
            {
                while (scriptdelegate());
            }));
            t.Start();
            return t;
        }

        public static void FxComposePanelFar(Panel panelcontainer, double animationduration, double starttime, List<FrameworkElement> excludeelements)
        {
            panelcontainer.Dispatcher.BeginInvoke(() =>
            {
                double relativetime = starttime;
                for (int x = 0; x < panelcontainer.Children.Count; x++)
                {
                    FrameworkElement el = (FrameworkElement)panelcontainer.Children[x];
                    if (excludeelements == null || !excludeelements.Contains(el))
                    {
                        Animator.AnimateProperty(el, "GlobalOffsetX", -4000, 0, animationduration / 1.2, relativetime, null);
                        Animator.AnimateProperty(el, "GlobalOffsetY", -1000, 0, animationduration / 1.2, relativetime, null);
                        Animator.AnimateProperty(el, "GlobalOffsetZ", -6000, 0, animationduration / 1.2, relativetime, null);
                        Animator.AnimateProperty(el, "RotationY", -180.0, 0.0, animationduration * 1.1, relativetime, null);
                        Animator.AnimateProperty(el, "Opacity", 0.25, 1.0, animationduration * 1.0, relativetime, null);
                        relativetime += (animationduration / 5);
                    }
                }
            });
        }

        public static void FxDecomposePanelClose(Panel panelcontainer, double animationduration, double starttime, List<FrameworkElement> excludeelements)
        {
            double relativetime = starttime;
            panelcontainer.Dispatcher.BeginInvoke(() =>
            {
                for (int x = panelcontainer.Children.Count - 1; x >= 0; x--)
                {
                    FrameworkElement el = (FrameworkElement)panelcontainer.Children[x];
                    if (excludeelements == null || !excludeelements.Contains(el))
                    {
                        Animator.AnimateProperty(el, "GlobalOffsetX", 0, -200, animationduration / 1.2, relativetime, null);
                        Animator.AnimateProperty(el, "GlobalOffsetY", 0, 20, animationduration / 1.2, relativetime, null);
                        Animator.AnimateProperty(el, "GlobalOffsetZ", 0, 1200, animationduration / 1.2, relativetime, null);
                        Animator.AnimateProperty(el, "RotationY", 0.0, 90.0, animationduration * 1.1, relativetime, null);
                        Animator.AnimateProperty(el, "Opacity", 1.0, 0.5, animationduration / 2, relativetime, null);
                    }
                    relativetime += (animationduration / 4);
                }
            });
        }

        public static void FxComposePanelClose(Panel panelcontainer, double animationduration, double starttime, List<FrameworkElement> excludeelements)
        {
            panelcontainer.Dispatcher.BeginInvoke(() =>
            {
                double relativetime = starttime;
                for (int x = 0; x < panelcontainer.Children.Count; x++)
                {
                    FrameworkElement el = (FrameworkElement)panelcontainer.Children[x];
                    if (excludeelements == null || !excludeelements.Contains(el))
                    {
                        Animator.AnimateProperty(el, "GlobalOffsetX", -200, 0, animationduration / 1.2, relativetime, null);
                        Animator.AnimateProperty(el, "GlobalOffsetY", -20, 0, animationduration / 1.2, relativetime, null);
                        Animator.AnimateProperty(el, "GlobalOffsetZ", 1200, 0, animationduration / 1.2, relativetime, null);
                        Animator.AnimateProperty(el, "RotationY", 90.0, 0.0, animationduration * 1.1, relativetime, null);
                        Animator.AnimateProperty(el, "Opacity", 0.5, 1.0, animationduration * 1.1, relativetime, null);
                    }
                    relativetime += (animationduration / 4);
                }
            });
        }

        public static void FxFadePanelFar(Panel panelcontainer, double tovalue, double animationduration, double starttime, List<FrameworkElement> excludeelements)
        {
            double delay = 0;
            for (int x = 0; x < panelcontainer.Children.Count; x++)
            {
                FrameworkElement el = (FrameworkElement)panelcontainer.Children[x];
                if (excludeelements == null || !excludeelements.Contains(el))
                {
                    Animator.FxFadeFar(el, tovalue, animationduration, starttime + delay);
                    delay += (animationduration / 4);
                }
            }
        }


        public static void FxFadeFar(FrameworkElement el, double tovalue, double animationduration, double starttime)
        {
            Animator.AnimateProperty(el, "Opacity", el.Opacity, tovalue, animationduration, starttime, null);
            if (el.Opacity > tovalue)
            {
                Animator.AnimateProperty(el, "GlobalOffsetZ", 0, -150, animationduration, starttime, null);
            }
            else if (el.Opacity < tovalue)
            {
                Animator.AnimateProperty(el, "GlobalOffsetZ", -150, 0, animationduration, starttime, null);
            }
        }


        public static void FxFadeOut(FrameworkElement element, double duration, double startdelay)
        {
            Animator.AnimateProperty(element, "Opacity", 1.0, 0.0, duration, startdelay + 0.0, null);
        }


        public static void FxSlideLeftIn(FrameworkElement element, double startx, double startdelay)
        {
            Animator.AnimateProperty(element, "X", startx, 0, 0.25, startdelay + 0.0, null);
            Animator.AnimateProperty(element, "Opacity", 0.0, 1.0, 0.25, startdelay + 0.0, null);
        }

        public static void FxSuperimpose(FrameworkElement element, double startdelay)
        {
            Animator.AnimateProperty(element, "Opacity", 0.0, 1.0, 0.3, startdelay + 0.0, null);
            Animator.AnimateProperty(element, "Scale", 0.5, 1.0, 0.3, startdelay + 0.0, null);
            Animator.AnimateProperty(element, "Scale", 1.0, 1.2, 2.7, startdelay + 0.35, null);
            Animator.AnimateProperty(element, "Opacity", 1.0, 0.7, 2.7, startdelay + 0.35, null);
            Animator.AnimateProperty(element, "Scale", 1.0, 10.0, 0.5, startdelay + 3.0, null);
            Animator.AnimateProperty(element, "Opacity", 0.8, 0.0, 0.2, startdelay + 3.0, delegate(object sender, EventArgs args)
            {
                (sender as FrameworkElement).Visibility = System.Windows.Visibility.Collapsed;
            });
        }


        private static TransformGroup _getTransformGroup(FrameworkElement element)
        {
            TransformGroup tgr;
            if (!element.Resources.Contains("MIRIA_sb_TransformGroup"))
            {
                tgr = new TransformGroup();
                element.RenderTransform = tgr;
                element.Resources.Add("MIRIA_sb_TransformGroup", tgr);
            }
            else
            {
                tgr = (TransformGroup)element.Resources["MIRIA_sb_TransformGroup"];
            }
            return tgr;
        }


        private static PlaneProjection _getPlaneProjection(FrameworkElement element)
        {
            PlaneProjection ppr;
            if (!element.Resources.Contains("MIRIA_sb_PlaneProjection"))
            {
                ppr = new PlaneProjection();
                element.Projection = ppr;
                element.Resources.Add("MIRIA_sb_PlaneProjection", ppr);
            }
            else
            {
                ppr = (PlaneProjection)element.Resources["MIRIA_sb_PlaneProjection"];
            }
            return ppr;
        }    
    }
}
